Lua/Tutorials/Class basics
From JC2-MP Documentation
Classes are used everywhere in the JC2-MP API. Player is a class, as is Vehicle and Vector3. You can also define your own classes, which is covered in this tutorial.
Classes are not a standard language feature of Lua. However, because Lua is a simple and extendable language, you can create your own class system. There are various class systems you can find out there, but JC2-MP helpfully provides all the features of a class system for you.
Contents
Features
Classes can have the following features:
- Static variables
- Static functions
- Member variables
- Member functions
- A constructor
- Operators
Constructors
In order to create an instance of a class, you must define a constructor. This is done by defining a member function called __init.
-- Declare a class called MyClass. class("MyClass") -- Define the constructor for MyClass. Note how ':' is used. function MyClass:__init() print("Hello") end -- Create an instance of MyClass. instance = MyClass()
This will print "Hello".
Member variables
Class instances can have variables. In this respect, classes work a little similarly to tables.
While inside of a class function, the self keyword refers to the current instance of the class.
class("Person") function Person:__init() self.name = "Unnamed" end instance = Person() print(instance.name) instance.name = "Fredward" print(instance.name)
This will print "Unnamed" and then "Fredward".
Member functions
Note how you must use : to declare and call member functions.
class("MyClass") function MyClass:__init() self.value = 1 end function MyClass:DoubleValue() self.value = self.value * 2 end function MyClass:SetValue(newValue) self.value = newValue end function MyClass:PrintValue() print("My value is "..tostring(self.value)) end instance = MyClass() instance:PrintValue() instance:SetValue(32) instance:DoubleValue() instance:PrintValue()
The results will be 1 and 64.
Remember: variables use '.', while functions use ':'.
instance.myVariable = 123 instance:DoSomething()
Using events
To subscribe to an event using a class function, you must use the Events:Subscribe function which takes a class function and a class instance.
This example is self-explanatory; a DelayedPrint waits for a specified time and then prints. Note how the event subscription is kept track of and removed later.
class("DelayedPrint") function DelayedPrint:__init(message, delay) self.message = message self.delay = delay self.timer = Timer() self.event = Events:Subscribe("PreTick", self, self.PreTickFunction) end function DelayedPrint:PreTickFunction() if self.timer:GetSeconds() > self.delay then print(self.message) Events:Unsubscribe(self.event) end end DelayedPrint("This will print after 5 seconds", 5) DelayedPrint("This will print after 1 second", 1)
Note the differences between how Events:Subscribe is used in previous tutorials:
-- Regular function: Events:Subscribe("PreTick", MyFunction) -- Class function: Events:Subscribe("PreTick", self, self.MyFunction)
Classes can be a powerful feature. The DelayedPrint example could be done without using classes. You would have the global variables timer and event. But what if you want to create two delayed prints? You would have to create more variables, or store them in tables, which can get messy very quickly. However, using a class, you can very easily create as many as you want without worry.
A practical example would be a client script that allows you to fire rockets where you're aiming. You could have a Rocket class, which is given an initial position and velocity in its constructor and travels along until it hits something, using raycasts. Like with the DelayedPrint example, Rocket would subscribe to PreTick and, when it hits something, unsubscribe from the event and explode. The glory of classes comes in when you quickly fire rockets everywhere and have all these class instances doing their thing, having their own events run and checking their own raycasts, and the code for it is very easy to work with and understand.
Static variables and functions
These don't require instances of classes. They are very similar to tables in this way, so their usefulness is limited.
When classes are constructed, they inherit any static variables and functions.
class("MyClass") MyClass.myStaticVariable = "I'm a static variable" function MyClass.MyStaticFunction() print(MyClass.myStaticVariable) end function MyClass:__init() self.MyStaticFunction() end instance = MyClass() instance.MyStaticFunction()
This will print "I'm a static variable" twice.
Additional notes
Member function declarations
With function MyClass:MyFunction() end, the ':' automatically adds a self variable as the first arg.
When you do something like:
function MyClass:MyFunction(arg1, arg2) end
Is the same as writing it as:
function MyClass.MyFunction(self, arg1, arg2) end
Keep in mind, that this doesn't work when defining functions without using syntactic sugar, i.e. as:
function MyClass.MyFunction = function(self, arg1, arg2) end
In above example, self has to be added manually, otherwise all arguments will be shifted by one
Calling member functions
When doing myInstance:MyFunction(1, 2), it's the same as doing MyClass.MyFunction(myInstance, 1, 2)
Events:Subscribe
There are many ways to use Events:Subscribe. All of this is valid:
class("MyClass") function MyClass:__init() Events:Subscribe("SomeEvent", self, self.MyFunction) Events:Subscribe("SomeEvent", self, MyClass.MyFunction) end function MyClass:MyFunction() end instance = MyClass() Events:Subscribe("SomeEvent", instance, instance.MyFunction) Events:Subscribe("SomeEvent", instance, MyClass.MyFunction)